package optplanner;

import index.metadata.IndexInfo;
import index.query.IndexJoinPlan;
import index.query.IndexSelectPlan;
import index.query.LookupPlan;
import server.starDB;
import storage.multibuffer.MultiBufferProductPlan;
import storage.record.Schema;
import storage.tx.Transaction;

import java.util.Map;

import compiler.absyn.Constant;
import compiler.absyn.Predicate;
import engine.query.Plan;
import engine.query.SelectPlan;
import engine.query.TablePlan;

import materialize.MaterializePlan;

/**
 * This class enhances class TablePlanner to consider the
 * possibility of using index-based and multibuffer operators.
 * @author Edward Sciore
 */
class TablePlanner {
	private TablePlan myplan;
	private Predicate mypred;
	private Schema myschema;
	private	Map<String,IndexInfo> indexes;
	private Transaction tx;

	/**
	 * Creates a new table planner.
	 * The specified predicate applies to the entire query.
	 * The table planner is responsible for determining
	 * which portion of the predicate is useful to the table,
	 * and when indexes are useful.
	 * @param tblname the name of the table
	 * @param mypred the query predicate
	 * @param tx the calling transaction
	 */
	public TablePlanner(String tblname, Predicate mypred, Transaction tx) {
		this.mypred  = mypred;
		this.tx  = tx;
		myplan   = new TablePlan(tblname, tx);
		myschema = myplan.schema();
		indexes  = starDB.mdMgr().getIndexInfo(tblname, tx);
	}

	/**
	 * Constructs a select plan for the table.
	 * The plan will contain an indexselect operator, if possible.
	 * @return a select plan for the table.
	 */
	public Plan makeSelectPlan() {
		Plan p = makeIndexSelect();
		if (p == null)
			p = myplan;
		return addSelectPred(p);
	}

	/**
	 * Constructs a join plan of the specified plan
	 * and the table.  The plan will contain an indexjoin
	 * operator, if possible.
	 * (Which means that if an indexselect is also possible,
	 * the indexjoin operator takes precedence.)
	 * The method returns null if no join is possible.
	 * @param current the specified plan
	 * @return a join plan of the plan and this table
	 */
	public Plan makeJoinPlan(Plan current) {
		Schema currsch = current.schema();
		Predicate joinpred = mypred.joinPred(myschema, currsch);
		if (joinpred == null)
			return null;
		Plan p = makeIndexJoin(current, currsch);
		if (p == null)
			p = makeProductJoin(current, currsch);
		return p;
	}

	/**
	 * Constructs a product plan of the specified plan and
	 * this table.  An indexselect operator is used if possible.
	 * @param current the specified plan
	 * @return a product plan of the specified plan and this table
	 */
	public Plan makeProductPlan(Plan current) {
		Plan p = addSelectPred(myplan);
		p = new MaterializePlan(p, tx);
		return new MultiBufferProductPlan(current, p, tx);
	}

	private Plan makeIndexSelect() {
		for (String fldname : indexes.keySet()) {
			Constant val = mypred.equatesWithConstant(fldname);
			if (val != null) {
				IndexInfo ii = indexes.get(fldname);
				Plan idxplan = new IndexSelectPlan(ii, val, tx);
				return new LookupPlan(idxplan, myplan);
			}
		}
		return null;
	}

	private Plan makeIndexJoin(Plan current, Schema currsch) {
		for (String fldname : indexes.keySet()) {
			String outerfield = mypred.equatesWithField(fldname);
			if (outerfield != null && currsch.hasField(outerfield)) {
				IndexInfo ii = indexes.get(fldname);
				Plan p = new IndexJoinPlan(current, ii, outerfield, tx);
				p = new LookupPlan(p, myplan);
				p = addSelectPred(p);
				return addJoinPred(p, currsch);
			}
		}
		return null;
	}

	private Plan makeProductJoin(Plan current, Schema currsch) {
		Plan p = makeProductPlan(current);
		return addJoinPred(p, currsch);
	}

	private Plan addSelectPred(Plan p) {
		Predicate selectpred = mypred.selectPred(myschema);
		if (selectpred != null)
			return new SelectPlan(p, selectpred);
		else
			return p;
	}

	private Plan addJoinPred(Plan p, Schema currsch) {
		Predicate joinpred = mypred.joinPred(currsch, myschema);
		if (joinpred != null)
			return new SelectPlan(p, joinpred);
		else
			return p;
	}
}
